package com.chenl.interview.service.impl;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chenl.interview.common.ErrorCode;
import com.chenl.interview.common.ThrowUtils;
import com.chenl.interview.constant.CommonConstant;
import com.chenl.interview.constant.QuestionConstant;
import com.chenl.interview.constant.UserConstant;
import com.chenl.interview.exception.BusinessException;
import com.chenl.interview.mapper.QuestionEsDao;
import com.chenl.interview.model.dto.question.QuestionAddRequest;
import com.chenl.interview.model.dto.question.QuestionEsDTO;
import com.chenl.interview.model.dto.question.QuestionQueryRequest;
import com.chenl.interview.model.entity.Question;
import com.chenl.interview.model.entity.QuestionBankQuestion;
import com.chenl.interview.model.entity.User;
import com.chenl.interview.model.vo.QuestionVO;
import com.chenl.interview.service.QuestionBankQuestionService;
import com.chenl.interview.service.QuestionService;
import com.chenl.interview.mapper.QuestionMapper;
import com.chenl.interview.utils.SqlUtils;
import com.jd.platform.hotkey.client.callback.JdHotKeyStore;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import static com.chenl.interview.constant.QuestionConstant.QUESTION_DETAIL;

/**
* @author chenl
* @description 针对表【question(题目)】的数据库操作Service实现
* @createDate 2024-12-29 15:33:22
*/
@Service
@Slf4j
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question>
    implements QuestionService{

    @Autowired
    private QuestionBankQuestionService questionBankQuestionService;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    private QuestionEsDao questionEsDao;

    @Resource
    private QuestionMapper questionMapper;

    @Override
    public void validQuestion(Question question, boolean add) {
        ThrowUtils.throwIf(question == null,new BusinessException(ErrorCode.PARAMS_ERROR));

        String title = question.getTitle();
        String content = question.getContent();
        String tags = question.getTags();
        String answer = question.getAnswer();
        Integer isVip = question.getIsVip();

        //如果是新增题目要求上述字段都不能为空
        if (add) {
            boolean anyBlank = StringUtils.isAnyBlank(title, content, tags, answer);
            anyBlank = anyBlank && isVip == null ? false : true;
            if (!anyBlank) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"参数存在错误");
            }
        }
        if (title.length() > 70){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"标题过长");
        }


    }

    @Override
    public Long addQuestion(QuestionAddRequest questionAddRequest) {
        ThrowUtils.throwIf(questionAddRequest == null,new BusinessException(ErrorCode.PARAMS_ERROR));

        Question question = BeanUtil.copyProperties(questionAddRequest, Question.class);
        List<String> tags = questionAddRequest.getTags();
        if (tags == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String jsonStr = JSONUtil.toJsonStr(tags);
        question.setTags(jsonStr);

        boolean save = this.save(question);
        if (!save){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"新增失败");
        }
        //数据同步到es
        List<Long> ids = Stream.of(question.getId()).collect(Collectors.toList());
        incSyncQuestionToEs(ids);

        return question.getId();
    }

    @Override
    public QuestionVO getQuestionVOById(User user, Long id) {
        //拼接hotkey
        String hotKey = QUESTION_DETAIL + id;
        //如果是hotKey，直接从jvm缓存中拿数据
        if (JdHotKeyStore.isHotKey(hotKey)) {
            Object obj = JdHotKeyStore.get(hotKey);
            if (obj != null) {
                QuestionVO cacheQuestionVO = (QuestionVO) obj;
                withVipPrivileges(cacheQuestionVO);
                return cacheQuestionVO;
            }
        }
        //不是热key，从数据库中获取
        Question question = this.getById(id);
        ThrowUtils.throwIf(question == null,new BusinessException(ErrorCode.NOT_FOUND_ERROR));

        QuestionVO questionVO = QuestionVO.objToVo(question);
        //判断该题目是否仅会员可见和当前登录用户是否会员（0表示非仅会员可见和非会员）
        withVipPrivileges(questionVO);

        //该key是热点就缓存数据
        JdHotKeyStore.smartSet(hotKey,questionVO);
        return questionVO;
    }

    @Override
    public boolean updateQuestion(Question question) {
        ThrowUtils.throwIf(question == null,new BusinessException(ErrorCode.PARAMS_ERROR));
        //参数校验
        validQuestion(question,false);

        //判断题目是否存在
        Long id = question.getId();
        Question oldQuestion = this.getById(id);
        ThrowUtils.throwIf(oldQuestion == null,new BusinessException(ErrorCode.NOT_FOUND_ERROR));

        //修改题目
        boolean result = this.updateById(question);
        if (!result) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }

        //同步数据到es
        List<Long> ids = Stream.of(id).collect(Collectors.toList());
        incSyncQuestionToEs(ids);

        return result;
    }

    @Override
    public QueryWrapper<Question> getQueryWrapper(QuestionQueryRequest questionQueryRequest) {
        QueryWrapper<Question> wrapper = new QueryWrapper<Question>();
        if (questionQueryRequest == null) {
            return wrapper;
        }
        Long id = questionQueryRequest.getId();
        String title = questionQueryRequest.getTitle();
        String content = questionQueryRequest.getContent();
        List<String> tags = questionQueryRequest.getTags();
        Integer isVip = questionQueryRequest.getIsVip();
        String sortField = questionQueryRequest.getSortField();
        String sortOrder = questionQueryRequest.getSortOrder();
        // JSON 数组查询
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                wrapper.like("tags", "\"" + tag + "\"");
            }
        }
        //模糊查询
        wrapper.like(StringUtils.isNotBlank(title),"title",title);
        wrapper.like(StringUtils.isNotBlank(content),"content",content);
        //精确查询
        wrapper.eq(id != null,"id",id);
        wrapper.eq(isVip != null,"isVip",isVip);
        //排序规则
        wrapper.orderBy(SqlUtils.validSortField(sortField),
                sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);

        return wrapper;
    }

    @Override
    public Page<QuestionVO> getQuestionVOPage(Page<Question> questionPage) {
        List<Question> questionList = questionPage.getRecords();
        Page<QuestionVO> questionVOPage = new Page<>(questionPage.getCurrent(), questionPage.getSize(), questionPage.getTotal());
        if (CollUtil.isEmpty(questionList)) {
            return questionVOPage;
        }
        // 对象列表 => 封装对象列表
        List<QuestionVO> questionVOList = questionList.stream().map(question -> {
            return QuestionVO.objToVo(question);
        }).collect(Collectors.toList());

        questionVOPage.setRecords(questionVOList);

        return questionVOPage;
    }

    @Override
    public Page<QuestionVO> searchFromEs(QuestionQueryRequest questionQueryRequest) {
        // 获取参数
        String searchText = questionQueryRequest.getSearchText();
        List<String> tags = questionQueryRequest.getTags();
        Integer isVip = questionQueryRequest.getIsVip();
        Long questionBankId = questionQueryRequest.getQuestionBankId();
        // 注意，ES 的起始页为 0
        int current = questionQueryRequest.getCurrent() - 1;
        int pageSize = questionQueryRequest.getPageSize();
        String sortField = questionQueryRequest.getSortField();
        String sortOrder = questionQueryRequest.getSortOrder();

        // 构造查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 过滤（查询没有逻辑删除的题目）
        boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
        if (isVip != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("isVip",isVip));
        }
        if (questionBankId != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("questionBankId",questionBankId));
        }
        // 必须包含所有标签
        if (CollUtil.isNotEmpty(tags)) {
            for (String tag : tags) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
            }
        }
        // 按关键词检索
        if (StringUtils.isNotBlank(searchText)) {
            boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText));
            boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText));
            boolQueryBuilder.minimumShouldMatch(1);
        }
        // 排序
        SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
        if (StringUtils.isNotBlank(sortField)) {
            sortBuilder = SortBuilders.fieldSort(sortField);
            sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);
        }
        // 分页
        PageRequest pageRequest = PageRequest.of(current, pageSize);
        // 构造查询
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(pageRequest)
                .withSorts(sortBuilder)
                .build();
        SearchHits<QuestionEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, QuestionEsDTO.class);
        // 复用 MySQL 的分页对象，封装返回结果
        Page<QuestionVO> page = new Page<>();
        page.setTotal(searchHits.getTotalHits());
        List<QuestionVO> resourceList = new ArrayList<>();
        if (searchHits.hasSearchHits()) {
            List<SearchHit<QuestionEsDTO>> searchHitList = searchHits.getSearchHits();
            for (SearchHit<QuestionEsDTO> questionEsDTOSearchHit : searchHitList) {
                //判断该题是不是热key
                String hotKey = QUESTION_DETAIL + questionEsDTOSearchHit.getId();
                if (JdHotKeyStore.isHotKey(hotKey)) {
                    QuestionVO cacheQuestionVo = (QuestionVO)JdHotKeyStore.get(hotKey);
                    if (cacheQuestionVo != null) {
                        withVipPrivileges(cacheQuestionVo);
                        resourceList.add(cacheQuestionVo);
                        continue;
                    }
                }
                //非热key，从数据库中获取题目
                Question question = this.getById(questionEsDTOSearchHit.getId());
                QuestionVO questionVO = BeanUtil.copyProperties(question, QuestionVO.class);
                withVipPrivileges(questionVO);
                resourceList.add(questionVO);

                JdHotKeyStore.smartSet(hotKey,questionVO);
            }
        }
        //返回题目视图
        page.setRecords(resourceList);
        return page;
    }

    @Override
    public void withVipPrivileges(QuestionVO questionVO) {
        //未登录的情况直接模糊处理
        if (!StpUtil.isLogin()) {
            //不显示题目答案 （动态截取,截取答案的百分之20，最多截取100个字符）
            String answer = questionVO.getAnswer();
            int truncationLength = (int) Math.ceil(answer.length() * QuestionConstant.TRUNCATION_RATIO);
            if (truncationLength > QuestionConstant.MAX_LENGTH){
                answer = answer.substring(0,QuestionConstant.MAX_LENGTH) + "...";
            }
            answer = answer.substring(0, Math.min(truncationLength, answer.length())) + "...";
            questionVO.setAnswer(answer);
            return;
        }
        //已登录，判断会员权限
        User user = (User) StpUtil.getSession().get(UserConstant.USER_LOGIN_STATE);
        if (user == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
        Integer questionIsVip = questionVO.getIsVip();
        Integer userIsVip = user.getIsVip();
        LocalDateTime vipExpiresAt = user.getVipExpiresAt();
        LocalDateTime now = LocalDateTime.now();
        if (userIsVip < questionIsVip || (vipExpiresAt != null && vipExpiresAt.isBefore(now))){
            //不显示题目答案 （动态截取,截取答案的百分之20，最多截取100个字符）
            String answer = questionVO.getAnswer();
            int truncationLength = (int) Math.ceil(answer.length() * QuestionConstant.TRUNCATION_RATIO);
            if (truncationLength > QuestionConstant.MAX_LENGTH){
                answer = answer.substring(0,QuestionConstant.MAX_LENGTH) + "...";
            }
            answer = answer.substring(0, Math.min(truncationLength, answer.length())) + "...";
            questionVO.setAnswer(answer);
        }
    }

    //增量同步
    @Override
    public void incSyncQuestionToEs(List<Long> ids) {
        List<Question> questions = questionMapper.listQuestionWithDelete(ids);
        List<QuestionEsDTO> questionEsDTOList = questions.stream()
                .map(QuestionEsDTO::objToDto)
                .collect(Collectors.toList());
        for (QuestionEsDTO questionEsDTO : questionEsDTOList) {
            Long id = questionEsDTO.getId();
            QueryWrapper<QuestionBankQuestion> queryWrapper = new QueryWrapper<QuestionBankQuestion>();
            queryWrapper.eq("questionId",id);
            QuestionBankQuestion one = questionBankQuestionService.getOne(queryWrapper);
            if (one != null){
                questionEsDTO.setQuestionBankId(one.getQuestionBankId());
            }
        }
        //分批批量执行
        final int pageSize = 500;
        int total = questionEsDTOList.size();
        log.info("IncSyncQuestionToEs start, total {}", total);
        for (int i = 0; i < total; i += pageSize) {
            int end = Math.min(i + pageSize, total);
            log.info("sync from {} to {}", i, end);
            questionEsDao.saveAll(questionEsDTOList.subList(i, end));
        }
        log.info("IncSyncQuestionToEs end, total {}", total);
    }
}






